package com.ziytek.web.citizen.service.impl;

import com.google.gson.Gson;
import com.ziytek.web.citizen.caffeine.CachePutOrGet;
import com.ziytek.web.citizen.config.caffeine.CaffeineUtil;
import com.ziytek.web.citizen.constant.NumConstant;
import com.ziytek.web.citizen.constant.ServerConstant;
import com.ziytek.web.citizen.constant.TableConstant;
import com.ziytek.web.citizen.enums.ErrorTypeEnum;
import com.ziytek.web.citizen.enums.StepTypeNoEnum;
import com.ziytek.web.citizen.mapper.BoardConfigMapper;
import com.ziytek.web.citizen.mapper.TableMapper;
import com.ziytek.web.citizen.model.bean.Batteries;
import com.ziytek.web.citizen.model.bean.BrattyRealData;
import com.ziytek.web.citizen.model.bean.CapStatsVo;
import com.ziytek.web.citizen.model.bean.OneBoardRealData;
import com.ziytek.web.citizen.model.req.RealtimeDataQueryReq;
import com.ziytek.web.citizen.model.rsp.RealtimeDataRsp;
import com.ziytek.web.citizen.model.vo.OperationVo;
import com.ziytek.web.citizen.model.vo.RealTimeDo;
import com.ziytek.web.citizen.model.vo.RealTimeVo;
import com.ziytek.web.citizen.model.vo.StatsDo;
import com.ziytek.web.citizen.pojo.basic.BoardConfig;
import com.ziytek.web.citizen.pojo.basic.DoorConfig;
import com.ziytek.web.citizen.service.*;
import com.ziytek.web.citizen.service.itf.LastRealtimeDataQueryService;
import com.ziytek.web.citizen.util.KeyUtil;
import com.ziytek.web.citizen.virtual.VirtualThreadPool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * @author fenghx
 * @version v1.0
 * @className QueryServer
 * @descripton 电池数据查询接口
 * @date 2023/4/11 15:49
 **/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LastRealtimeDataQueryServiceImpl implements LastRealtimeDataQueryService {
    private final Gson gson;
    private final TableMapper tableMapper;
    private final DoorsService doorsService;
    private final CachePutOrGet cachePutOrGet;
    private final BoardConfigMapper boardConfigMapper;
    private final CompressionService compressionService;
    private final OneBatteryService oneBatteryService;
    private final OperationVoCacheService operationVoCacheService;

    @Override
    public RealtimeDataRsp queryRealData(RealtimeDataQueryReq request) {
        var response = new RealtimeDataRsp();
        response.success();

        var doors = doorsService.getDoorsConfig();
        if (CollectionUtils.isEmpty(doors)) {
            return response;
        }

        List<String> doorNos = new ArrayList<>();
        for (DoorConfig doorConfig : doors) {
            doorNos.add(doorConfig.getDoor());
        }

        var cabinetNo = request.getCabinetNo();
        long start = System.currentTimeMillis();
        var side = request.getSide();
        Set<String> doorList = new HashSet<>();
        if (StringUtils.isNotBlank(side)) {
            if (ServerConstant.SIDE_A.equalsIgnoreCase(side)) {
                doorList.add(ServerConstant.DOOR_FRONT_LEFT);
                doorList.add(ServerConstant.DOOR_FRONT_RIGHT);
            }
            if (ServerConstant.SIDE_B.equalsIgnoreCase(side)) {
                doorList.add(ServerConstant.DOOR_BACK_LEFT);
                doorList.add(ServerConstant.DOOR_BACK_RIGHT);
            }
        }

        if (StringUtils.isBlank(side) && StringUtils.isNotBlank(request.getDoor())) {
            doorList.add(request.getDoor());
        }

        if (StringUtils.isBlank(side) && StringUtils.isBlank(request.getDoor())) {
            doorList.addAll(doorNos);
        }

        List<BrattyRealData> rsp = new ArrayList<>();
        for (var door : doorList) {
            var brattyRealData = oneBatteryService.oneBrattyRealData(Integer.parseInt(cabinetNo) + "", door);
            if (null == brattyRealData) {
                continue;
            }
            rsp.add(brattyRealData);
        }

        if(StringUtils.isNotBlank(request.getDoor())) {
            log.info("首页长连接推送处理数据耗时：{}-{}", System.currentTimeMillis() - start, gson.toJson(request));
        }

        response.setData(rsp);
        return response;
    }

    @Override
    public RealtimeDataRsp queryRealDataTwo(String cabinetNo, String side, String doorStr) {
        RealtimeDataQueryReq req = new RealtimeDataQueryReq();
        req.setSide(side);
        req.setDoor(doorStr);
        req.setCabinetNo(cabinetNo);
        return queryRealData(req);
    }

    private List<CapStatsVo> getStatsList(List<BoardConfig> boardConfigs, String cabinetNo) {
        List<CapStatsVo> statsList = new ArrayList<>();

        CountDownLatch latch = new CountDownLatch(1);

        Runnable task = () -> {
            try {
                for (var b : boardConfigs) {
                    var opKey = KeyUtil.commandOperateKey(cabinetNo, b.getAddress(), b.getChannelNo());
                    var vo = operationVoCacheService.getOperationVo(opKey);
                    var key = cabinetNo + CaffeineUtil.CACHE_KEY_SYMBOL + b.getAddress() + CaffeineUtil.CACHE_KEY_SYMBOL + b.getChannelNo();
                    var s = cachePutOrGet.getStatsCache(key);
                    //log.info("opkey:{}-OperationVo-{},stats:{}", opKey, gson.toJson(vo), gson.toJson(s));
                    if (null != s) {
                        s.setIv(Math.abs(s.getI()), Math.abs(s.getV()));
                    }

                    // 没有工步方案执行操作记录缓存
                    if (null == vo) {
                        // 电池电压有数据待工艺状态数据,否则未空闲状态数据
                        //log.info("电池电压有数据vo操作缓存未找到cabinetNo:{} - boardNo:{} - channelNo:{} - voltage:{} - opKey:{}", cabinetNo, b.getAddress(), b.getChannelNo(), s != null ? s.getV() : null, opKey);
                        statsList.add(null != s && s.getV() > NumConstant.INT_TWO_THOUSAND ? s : getEmptyStats(key));
                        continue;
                    }

                    // 异常状态
                    if (ServerConstant.START_FLAG_ERROR == vo.getStartFlag()) {
                        statsList.add(null != s && s.getV() > NumConstant.INT_TWO_THOUSAND ? s : getEmptyStats(key));
                        continue;
                    }

                    // 已完成的通道查询放电数据，放电数据没有已当前的缓存数据
                    if (ServerConstant.START_FLAG_FINISH == vo.getStartFlag()) {
                        // 查询已经放电完成数据的统计数据
                        disChargeStats(cabinetNo, statsList, b, vo, key, s);
                        continue;
                    }

                    // 终止状态 保留当前容量及时间数据,电池电压为电池实时数据
                    if (ServerConstant.START_FLAG_DISABLE == vo.getStartFlag()) {
                        CapStatsVo cvo = null;

                        if (s != null && s.getV() > NumConstant.INT_TWO_THOUSAND
                                && s.getV() != (int) NumConstant.FLOAT_PWM_ERROR
                                && s.getV() != (int) NumConstant.FLOAT_CHECK_ERROR
                                && s.getV() != (int) NumConstant.FLOAT_BATTERY_LIMIT) {
                            s.setT(ErrorTypeEnum.STOP.getCode());
                            s.setCty(StepTypeNoEnum.STOP.getCode());

                            // 查询有没有放电数据
                            cvo = disChargeStatsStop(cabinetNo, statsList, b, vo, key, s);
                            if (null != cvo) {
                                cvo.setT(ErrorTypeEnum.STOP.getCode());
                            }
                        }

                        statsList.add(null != cvo ? cvo : null != s ? s : getEmptyStats(key));
                        continue;
                    }

                    // 进行中状态
                    if (ServerConstant.START_FLAG_NORMAL == vo.getStartFlag()) {
                        if (s != null && s.getV() > NumConstant.INT_TWO_THOUSAND
                                && s.getT() != ErrorTypeEnum.EXECUTING.getCode()
                                && s.getV() != (int) NumConstant.FLOAT_PWM_ERROR
                                && s.getV() != (int) NumConstant.FLOAT_CHECK_ERROR
                                && s.getV() != (int) NumConstant.FLOAT_BATTERY_LIMIT) {
                            s.setT(ErrorTypeEnum.EXECUTING.getCode());
                        }
                    }

                    // 工步执行中，展示实时统计的数据
                    statsList.add(null == s ? getEmptyStats(key) : s);
                }
            } catch (Exception e) {
                log.info("异常", e);
            } finally {
                latch.countDown();
            }
        };

        VirtualThreadPool.execute(task);

        try {
            // 等待任务完成
            latch.await();
        } catch (Exception e) {
            log.error("异常", e);
        }

        //log.info("总处理耗时-response {}", statsList.size());
        return statsList;
    }

    /**
     * 放电容量数据
     */
    private void disChargeStats(String cabinetNo, List<CapStatsVo> statsList, BoardConfig b, OperationVo vo, String key, CapStatsVo s) {
        var sdo = tableMapper.queryStatsDoLast(TableConstant.TABLE_REAL_TIME_STATS, Integer.parseInt(cabinetNo), vo.getOperationId(), b.getAddress(), b.getChannelNo());
        if (null != sdo && null != sdo.getData()) {
            hasStatsData(sdo, key, statsList, s);
        } else {
            // 没有放电结束的统计数据,而且不是异常数字
            if (null != s && s.getV() > NumConstant.INT_TWO_THOUSAND
                    && s.getV() != (int) NumConstant.FLOAT_PWM_ERROR
                    && s.getV() != (int) NumConstant.FLOAT_CHECK_ERROR
                    && s.getV() != (int) NumConstant.FLOAT_BATTERY_LIMIT) {
                s.setT(ErrorTypeEnum.EXECUTION_COMPLETED.getCode());
            }
            statsList.add(null != s ? s : getEmptyStats(key));
        }
    }

    /**
     * 放电容量数据
     */
    private CapStatsVo disChargeStatsStop(String cabinetNo, List<CapStatsVo> statsList, BoardConfig b, OperationVo vo, String key, CapStatsVo s) {
        var sdo = tableMapper.queryStatsDoLast(TableConstant.TABLE_REAL_TIME_STATS, Integer.parseInt(cabinetNo), vo.getOperationId(), b.getAddress(), b.getChannelNo());
        return null != sdo && null != sdo.getData() ? hasStatsDataVo(sdo, key, statsList, s) : null;
    }


    private void hasStatsData(StatsDo sdo, String key, List<CapStatsVo> statsList, CapStatsVo s) {
        byte[] data;
        try {
            data = compressionService.decompress(sdo.getData());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        var v = gson.fromJson(new String(data, StandardCharsets.UTF_8), RealTimeVo.class);
        // 没数据
        if (v == null) {
            statsList.add(null != s ? s : getEmptyStats(key));
            return;
        }

        var stats = new CapStatsVo();
        stats.setK(key);
        stats.setIv(Math.abs(s.getI()), Math.abs(s.getV()));

        stats.setT(v.getT());
        stats.setCty(v.getCty());

        stats.setC(v.getCpa());
        stats.setAc(v.getAc());
        //stats.setTs(v.getTs());
        // 完成时间去同时总时间
        stats.setTs(null != sdo.getTime() ? sdo.getTime().doubleValue() : v.getTs());
        statsList.add(stats);
    }

    private CapStatsVo hasStatsDataVo(StatsDo sdo, String key, List<CapStatsVo> statsList, CapStatsVo s) {
        byte[] data;
        try {
            data = compressionService.decompress(sdo.getData());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        var v = gson.fromJson(new String(data, StandardCharsets.UTF_8), RealTimeVo.class);
        // 没数据
        if (null == v) {
            statsList.add(null != s ? s : getEmptyStats(key));
            return null;
        }

        var stats = new CapStatsVo();
        stats.setK(key);
        stats.setIv(Math.abs(s.getI()), Math.abs(s.getV()));

        stats.setT(v.getT());
        stats.setCty(v.getCty());

        stats.setC(v.getCpa());
        stats.setAc(v.getAc());
        //stats.setTs(v.getTs());
        // 完成时间去同时总时间
        stats.setTs(null != sdo.getTime() ? sdo.getTime().doubleValue() : v.getTs());

        return stats;
    }

    public CapStatsVo getEmptyStats(String key) {
        var stats = new CapStatsVo();
        stats.setIv(NumConstant.INT_ZERO, NumConstant.INT_ZERO);
        stats.setC(NumConstant.DOUBLE_ZERO);
        stats.setTs(NumConstant.DOUBLE_ZERO);
        stats.setAc(NumConstant.DOUBLE_ZERO);
        stats.setK(key);
        stats.setCty("");
        stats.setT(ErrorTypeEnum.LEISURE.getCode());

        return stats;
    }

    protected List<BrattyRealData> getBrattyRealData(Map<String, List<RealTimeDo>> realTimeDoMap, String queryDoor) {
        if (CollectionUtils.isEmpty(realTimeDoMap) || StringUtils.isEmpty(queryDoor)) {
            return Collections.emptyList();
        }

        var pair = doorsService.getBoardNos(queryDoor);
        if (null == pair || CollectionUtils.isEmpty(pair.getLeft()) || CollectionUtils.isEmpty(pair.getRight())) {
            return Collections.emptyList();
        }

        return getBrattyRealData(getStringBatteriesMap(realTimeDoMap, pair.getLeft(), pair.getRight()));
    }

    private Map<String, Batteries> getStringBatteriesMap(Map<String, List<RealTimeDo>> realTimeDoMap, Set<Integer> boardNos, List<String> doorNos) {
        Map<String, Batteries> batteriesMap = new ConcurrentHashMap<>();
        CountDownLatch latch = new CountDownLatch(1);

        Runnable task = () -> {
            try {
                for (var entry : realTimeDoMap.entrySet()) {
                    var split = entry.getKey().split(CaffeineUtil.CACHE_KEY_CONNECTION_SYMBOL);
                    var boardAddress = Integer.parseInt(split[NumConstant.INT_ONE]);
                    if (!boardNos.contains(boardAddress)) {
                        continue;
                    }

                    for (var o : entry.getValue()) {
                        extracted(doorNos, o, boardAddress, batteriesMap);
                    }
                }
            } catch (Exception e) {
            } finally {
                latch.countDown();
            }
        };

        VirtualThreadPool.execute(task);

        try {
            // 等待任务完成
            latch.await();
        } catch (Exception e) {
            log.error("异常", e);
        }

        return batteriesMap;
    }

    private void extracted(List<String> doorNos, RealTimeDo o, int boardAddress, Map<String, Batteries> batteriesMap) {
        var cellNum = o.getCnn();
        var boardConfig = boardConfigMapper.queryByChannelNo(boardAddress, cellNum);
        if (null == boardConfig || !doorNos.contains(boardConfig.getDoor())) {
            return;
        }

        var door = boardConfig.getDoor();
        var lineNo = boardConfig.getLineNo();
        var key = door + "," + lineNo + "," + cellNum;

        byte[] data;
        try {
            data = compressionService.decompress(o.getData());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        var vo = gson.fromJson(new String(data, StandardCharsets.UTF_8), RealTimeVo.class);
        if (null != vo) {
            batteriesMap.put(key, getBatteries(cellNum, vo));
        }
    }

    private Batteries getBatteries(int cellNum, RealTimeVo v) {
        var batteries = new Batteries();
        batteries.setPosition(cellNum);
        batteries.setCurrent(v.getC() * NumConstant.FLOAT_ONE);
        batteries.setVoltage(v.getV() * NumConstant.FLOAT_ONE);

        int t = ((Double) v.getTs()).intValue() / NumConstant.INT_TEN;
        int y = ((Double) v.getTs()).intValue() % NumConstant.INT_TEN;
        int time = y == NumConstant.INT_ZERO ? t * NumConstant.INT_TEN : (t + NumConstant.INT_ONE) * NumConstant.INT_TEN;

        var duration = String.format("%.2f", (time / NumConstant.FLOAT_SIXTY));

//        if (cellNum == 1) {
//            log.info("时间:{}-{}-{}", v.getTs(), time, duration);
//        }
        batteries.setExecutionDuration(Float.parseFloat(duration));
        var capacity = String.format("%.2f", (v.getCpa()));
        batteries.setCapacity(Float.parseFloat(capacity));
        batteries.setExceptionType(v.getT());
        batteries.setCommandType(v.getCty());

        return batteries;
    }

    private List<BrattyRealData> getBrattyRealData(Map<String, Batteries> batteriesMap) {
        if (CollectionUtils.isEmpty(batteriesMap)) {
            return Collections.emptyList();
        }

        List<BrattyRealData> realDataList = new ArrayList<>();
        Map<String, OneBoardRealData> oneBoardRealDataVoHashMap = new ConcurrentHashMap<>();
        for (var key : batteriesMap.keySet()) {
            extracted(batteriesMap, key, oneBoardRealDataVoHashMap);
        }


        Map<String, BrattyRealData> brattyRealDataMap = new HashMap<>();
        for (var key : oneBoardRealDataVoHashMap.keySet()) {
            extracted(key, brattyRealDataMap, oneBoardRealDataVoHashMap);
        }

        for (var entry : brattyRealDataMap.entrySet()) {
            realDataList.add(entry.getValue());
        }
        return realDataList;
    }

    private void extracted(String key, Map<String, BrattyRealData> brattyRealDataMap, Map<String, OneBoardRealData> oneBoardRealDataVoHashMap) {
        var split = key.split(",");
        var door = split[NumConstant.INT_ZERO];
        var line = split[NumConstant.INT_ONE];

        var brattyRealData = brattyRealDataMap.get(door);
        if (null == brattyRealData) {
            brattyRealData = new BrattyRealData();
            brattyRealData.setDoor(door);
        }

        var doorData = brattyRealData.getDoorData();
        if (null == doorData) {
            doorData = new ArrayList<>();
        }

        var has = doorData.stream().anyMatch(d -> null != d && StringUtils.isNotBlank(d.getLine()) && d.getLine().equalsIgnoreCase(line));
        var oneBoardRealData = oneBoardRealDataVoHashMap.get(key);
        if (!has) {
            doorData.add(oneBoardRealData);
            brattyRealData.setDoorData(doorData);
        }

        brattyRealDataMap.put(door, brattyRealData);
    }

    private void extracted(Map<String, Batteries> batteriesMap, String key, Map<String, OneBoardRealData> oneBoardRealDataVoHashMap) {
        var split = key.split(",");
        var door = split[NumConstant.INT_ZERO];
        var line = split[NumConstant.INT_ONE];
        var cellNum = split[NumConstant.INT_TWO];

        var oneBoardRealData = oneBoardRealDataVoHashMap.get(door + "," + line);
        if (null == oneBoardRealData) {
            oneBoardRealData = new OneBoardRealData();
            oneBoardRealData.setLine(line);
        }

        var batteries = batteriesMap.get(key);
        var batteriesList = oneBoardRealData.getBatteriesList();
        if (null == batteriesList) {
            batteriesList = new ArrayList<>();
        }

        var has = batteriesList.stream().anyMatch(b -> null != b && Integer.parseInt(cellNum) == b.getPosition());
        if (!has) {
            batteriesList.add(batteries);
            batteriesList.sort(Comparator.comparing(Batteries::getPosition));
            oneBoardRealData.setBatteriesList(batteriesList);
        }

        oneBoardRealDataVoHashMap.put(door + "," + line, oneBoardRealData);
    }
}
